/**
 * @file vsnprintf.c
 * @author DY Young (dyyoung@os.email.cn)
 * @brief format string output is implemented
 *        in this file. And the output is directed to
 *        serial by device manager.
 * @version 0.1
 * @date 2023-07-01
 *
 * @copyright Copyright (c) 2023
 *
 */
#include "strapi.h"

const char _little_case_[] = "0123456789abcdef";
const char _big_case_[] = "0123456789ABCDEF";

// %[flags][width][.precision][length]specifier
/**REVIEW - copy char for cont to buffer within stop*/
static inline void
__reverse_copy(char *buffer, char *stop, const char *cont, int ptr)
{
    for (; ptr >= 0 && (buffer < stop); ptr--, buffer++)
    {
        *buffer = cont[ptr];
    }
}

/**NOTE - the return value is the output string length without end char*/
static int __print_hex(char *buffer, char *stop, unsigned num)
{
    int ptr = -1;
    char _tmp_num[19];
    do
    {
        _tmp_num[++ptr] = _little_case_[num % 16];
        num = num >> 4;
    } while (num);

    __reverse_copy(buffer, stop, _tmp_num, ptr);
    return ptr + 1;
}

static int __print_lld(char *buffer, char *stop, long long num, int padding, char pc)
{
    int ptr = -1;
    char _tmp_num[19];

    do
    {
        ++ptr;
        _tmp_num[ptr] = _little_case_[num % 10];
        num = num / 10;
    } while (num);
    while ((ptr + 1) < padding)
    {
        ++ptr;
        _tmp_num[ptr] = pc;
    }

    __reverse_copy(buffer, stop, _tmp_num, ptr);
    return ptr + 1;
}

static int __print_dec(char *buffer, char *stop, int num_s, int padding, char pc, char direction)
{
    int ptr = -1;
    char _tmp_num[19];

    int num = num_s < 0 ? ~num_s + 1 : num_s;

    do
    {
        ++ptr;
        _tmp_num[ptr] = _little_case_[num % 10];
        num = num / 10;
    } while (num);

    if (num_s < 0)
    {
        ++ptr;
        _tmp_num[ptr] = '-';
    }

    if (direction == 'R')
    {
        while ((ptr + 1) < padding)
        {
            ++ptr;
            _tmp_num[ptr] = pc;
        }
    }

    __reverse_copy(buffer, stop, _tmp_num, ptr);

    if (direction == 'L')
    {

        while ((ptr + 1) < padding)
        {
            ++ptr;
            *(buffer + ptr) = pc;
        }
    }
    return ptr + 1;
}

static int __print_str(char *buffer, char *stop, const char *str, int padding)
{
    int ptr = 0;
    for (; (buffer < stop) && (*str); buffer++, str++)
    {
        *buffer = *str;
        ptr++;
    }
    for (; ptr < padding; ptr++, buffer++)
    {
        *buffer = ' ';
    }
    *buffer = '\0';

    return ptr;
}

/**
 * @brief variable string numbered print formatted
 *
 * @param buffer to save the result
 * @param length max output size limit
 * @param fmt   format
 * @param args args
 * @return int the formatted result size
 */
int vsnprintf(char *buffer, int length, const char *fmt, va_list args)
{
    int len_left = length;
    char *const stop = buffer + length;
    for (; *fmt && (len_left > 1); fmt++)
    {
        const char c = *fmt;
        if (c != '%')
        {
            *buffer = c;
            buffer++;
            len_left--;
        }
        else
        {
            fmt++;
            int off = 0;
            char *str;
            char lc;
            int num;
            unsigned int unum;
            unsigned long long lnum;

            /**processing padding*/
            int padding = 0;
            char padc = ' ';
            char direction = 'L';
            if (*fmt == '-')
            {
                direction = 'R';
                fmt++;
            }
            if (*fmt == '0')
            {
                padc = '0';
            }
            while (*fmt >= '0' && *fmt <= '9')
            {
                padding = padding * 10 + (*fmt - '0');
                fmt++;
            }

            /**
             * NOTE: escaped character has already processed by compiler.
             *       therefore we do not need handle this.
             */
            if (*fmt == 'h')
            {
                // process prefix
                fmt++;
            }
            switch (*fmt)
            {
            case '%':
                *buffer = '%';
                off = 1;
                break;
            case 'u':
                num = va_arg(args, unsigned);
                off = __print_dec(buffer, stop, num, padding, padc, direction);
                break;
            case 'd':
                num = va_arg(args, int);
                off = __print_dec(buffer, stop, num, padding, padc, direction);
                break;
            case 'p':
            case 'x':
            case 'X':
                num = va_arg(args, unsigned);
                off = __print_hex(buffer, stop, num);
                break;
            case 's':
                str = va_arg(args, char *);
                off = __print_str(buffer, stop, str, padding);
                break;
            case 'l':
                if (*(fmt + 1) == 'l')
                    if (*(fmt + 2) == 'd')
                        fmt += 2;
                lnum = va_arg(args, long long);
                off = __print_lld(buffer, stop, lnum, padding, padc);
                break;
            case 'c':
                unum = va_arg(args, unsigned int);
                lc = unum & 0xff;
                *buffer = (char)(lc);
                off = 1;
                break;
            default:
                break;
            }
            buffer += off;
            len_left -= off;
        }
    }
    *buffer = '\0';
    return length - len_left;
}
